/*
 * Tf: a unit test framework for C
 * Copyright (C) 2012 zrezek at gmail.com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include "tf.h"
#include "tf-private.h"
#include <Windows.h>
#include <assert.h>

struct thread_params
{
    struct tf_fixture* fixture;
    struct tf_test* test;
};

static LONG WINAPI exception_filter(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
    printf("failed unhandled exception\n");
    return EXCEPTION_EXECUTE_HANDLER;
}


static DWORD WINAPI execute_thread_func(LPVOID parameter)
{
    struct thread_params* params = (struct thread_params*) parameter;

    tf_invoke_test_local(params->fixture, params->test);
    return 0;
}

static void setup_thread_params(struct thread_params* params)
{
    struct tf_fixture* fixture = (struct tf_fixture*) tf_state.fixtures.first;

    assert(params);

    // search for fixture & test to run, put them into params
    // structure and return
    while (fixture->node.next)
    {
        if (!strcmp(fixture->path_and_name, tf_state.fixture_to_execute))
        {
            struct tf_test* test = (struct tf_test*) fixture->tests.first;

            while (test->node.next)
            {
                if (!strcmp(test->name, tf_state.test_to_execute))
                {
                    params->fixture = fixture;
                    params->test = test;
                    return;
                }
                test = (struct tf_test*) test->node.next;
            }
        }
        fixture = (struct tf_fixture*) fixture->node.next;
    }

    // if we reach this place it means that code above
    // failed to find fixture & test to execute
    assert(0);
}

void tf_execute_test_remote()
{
    DWORD timeout;
    HANDLE thread;
    struct thread_params params;

    setup_thread_params(&params);

    SetUnhandledExceptionFilter(exception_filter);

    thread = CreateThread(NULL,
                            0,
                            execute_thread_func,
                            &params,
                            0,
                            0);

    if (!thread)
    {
        printf("CreateThread() failed %d\n", GetLastError());
        return;
    }

    timeout = params.fixture->timeout ? 
            params.fixture->timeout * 1000 : INFINITE;

    if (WaitForSingleObject(thread, timeout) == WAIT_TIMEOUT)
    {
        TerminateThread(thread, 0);
        printf("failed, timeout %d sec reached\n", params.fixture->timeout);
        return;
    }
}

void tf_invoke_test_remote(struct tf_fixture* fixture, struct tf_test* test)
{
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    DWORD error;

    int cmdline_size;
    char* cmdline;

    assert(fixture && test);

    cmdline_size = strlen("--invoke")
                        + strlen(tf_state.executable_name)
                        + strlen(fixture->path_and_name)
                        + strlen(test->name)
                        + 4;
    cmdline = (char*) malloc(cmdline_size);
    sprintf(cmdline, "%s --invoke %s %s", tf_state.executable_name,
                                        fixture->path_and_name,
                                        test->name);


    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // Start the child process.
    if (!CreateProcessA(0,                       // No module name (use command line). 
                        cmdline,                 // Command line. 
                        NULL,                    // Process handle not inheritable. 
                        NULL,                    // Thread handle not inheritable. 
                        FALSE,                   // Set handle inheritance to FALSE. 
                        0,                       // No creation flags. 
                        NULL,                    // Use parent's environment block. 
                        NULL,                    // Use parent's starting directory. 
                        &si,                     // Pointer to STARTUPINFO structure.
                        &pi))                    // Pointer to PROCESS_INFORMATION structure.
    {
        printf("CreateProcessA() failed %d\n", GetLastError());
        free(cmdline);
        return;
    }

    // Wait until child process exits.
    WaitForSingleObject(pi.hProcess, INFINITE);

    // Close process and thread handles. 
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    free(cmdline);
}
